use std::clone::Clone;
use std::path::PathBuf;
use std::string::ToString;
use std::sync::Arc;

use super::Operation;
use crate::core::error::operation_error::OperationError;
use crate::core::path_type::TypedPath;
use crate::core::step_context::StepContext;
use snafu::{ResultExt, Snafu};
use tonic::async_trait;

#[derive(Debug, Snafu)]
#[snafu(visibility(pub(crate)))]
pub enum Error {
    #[snafu(display("Failed to clone {url} to {dest:?}: {source}"))]
    Clone {
        url: String,
        dest: PathBuf,
        source: std::io::Error,
    },

    #[snafu(display("Failed to reset repository: {source}"))]
    Reset { source: std::io::Error },

    #[snafu(display("Failed to clean repository: {source}"))]
    Clean { source: std::io::Error },

    #[snafu(display("Failed to pull repository: {source}"))]
    Pull { source: std::io::Error },
}

impl From<Error> for OperationError {
    fn from(value: Error) -> Self {
        OperationError::Git { source: value }
    }
}

pub struct Git {
    url: String,
    path: TypedPath,
    branch: String,
}

impl Git {
    pub fn new(url: &str, path: &TypedPath, branch: &str) -> Self {
        Git {
            url: url.to_string(),
            path: path.clone(),
            branch: branch.to_string(),
        }
    }
}

#[async_trait]
impl Operation for Git {
    async fn run(&mut self, context: Arc<StepContext>) -> Result<(), OperationError> {
        let path = match &self.path {
            TypedPath::Normal(p) => p.clone(),
            TypedPath::Contextual(s) => context.job_context().path().join(s),
        };

        if !path.exists() {
            let path = path.to_string_lossy();
            context
                .run_cmd(
                    "git",
                    [
                        "clone",
                        "--depth",
                        "1",
                        "--recurse-submodule",
                        "--shallow-submodules",
                        "-b",
                        &self.branch,
                        &self.url,
                        &*path,
                    ],
                )
                .await
                .context(CloneSnafu {
                    url: self.url.clone(),
                    dest: path.to_string(),
                })?;
        }

        context
            .run_cmd_at("git", ["reset", "--hard"], &path)
            .await
            .context(ResetSnafu {})?;
        context
            .run_cmd_at("git", ["clean", "-df"], &path)
            .await
            .context(CleanSnafu {})?;
        context
            .run_cmd_at("git", ["pull"], &path)
            .await
            .context(PullSnafu)?;

        Ok(())
    }
}
